package yicr.untils.gis;


import org.gavaghan.geodesy.Ellipsoid;
import org.gavaghan.geodesy.GeodeticCalculator;
import org.gavaghan.geodesy.GlobalCoordinates;
import yicr.untils.NslUtilCsv;
import yicr.untils.gis.Polygon.NslPolygon;
import yicr.untils.gis.domain.NslGpsPoint;
import yicr.untils.gis.domain.NslPoint;
import yicr.untils.gis.domain.NslPointLong;

import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.util.*;

public class NslUtilGis {

    private static double EARTH_RADIUS = 6378137;

    private static double rad(double d) {
        return d * Math.PI / 180.0;
    }

    /**
     * 通过经纬度获取距离(单位：米)
     *
     * @param lat1 纬度1
     * @param lng1 经度1
     * @param lat2 纬度2
     * @param lng2 经度2
     * @return 距离
     */
    public static double getDistance(double lat1, double lng1, double lat2,
                                     double lng2) {
        double radLat1 = rad(lat1);
        double radLat2 = rad(lat2);
        double a = radLat1 - radLat2;
        double b = rad(lng1) - rad(lng2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
                Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000d) / 10000d;
        //double len=s/EARTH_SEA;
        //s = s / 1000;
        return s;
    }

    // 对10w 个河南联通、电信 站址进行处理，运行时间大概为6757s， 独立站点为65762
    // 如果采用经纬度保留4位，去重的话，出来下来是81217个独立经纬度
    // 用50米明显要准确度高很多
    public static void groupByDistance(String inputFileName,String outputFileName,double range) throws IOException {
        //RANGE 该距离内为同一站址
        //double RANGE=150;

        long startTime=System.currentTimeMillis();   //获取开始时间
        List<String> lines= NslUtilCsv.readCsvFile(new File(inputFileName));
        Set<NslGpsPoint> pointSet=new HashSet<NslGpsPoint>();

        //ouput list
        List<String> outputList=new ArrayList<>();
        String head="id,longitude,latitude";
        outputList.add(head);

        for (int i = 1; i < lines.size(); i++) {
            //System.out.println(lines.get(i));
            String[] row=lines.get(i).split(",");
            String idString=  row[0];
            NslGpsPoint NslGpsPoint=new NslGpsPoint(idString,Double.parseDouble(row[1]),Double.parseDouble(row[2]));
            pointSet.add(NslGpsPoint);
            //System.out.println(NslGpsPoint);
        }
        int j=100;
        //while(pointSet.size()>0){
        while(pointSet.size()>0){
            //System.out.println("size = "+pointSet.size());

            Iterator<NslGpsPoint> it=pointSet.iterator();
            NslGpsPoint srcPoint = it.next();
            String outputRow=srcPoint.getIdString()+","+srcPoint.getLongitude()+","+srcPoint.getLatitude();
            GlobalCoordinates source = new GlobalCoordinates(srcPoint.getLatitude(), srcPoint.getLongitude());
            it.remove();
            String ids="";
            while (it.hasNext()){
                NslGpsPoint targetPoint=it.next();
                GlobalCoordinates target = new GlobalCoordinates(targetPoint.getLatitude(), targetPoint.getLongitude());
                double distance=getDistanceMeter(source,target, Ellipsoid.Sphere);
                if(distance<range){
                    ids=ids+":"+targetPoint.getIdString();
                    it.remove();
                }
            }
            if(!ids.equals("")){
                outputRow=outputRow+","+ids;
            }
            outputList.add(outputRow);
            j--;
        }

        System.out.println("*********  output ********");


        //String outputFile="D:\\Programming\\Test\\points_output.csv";
        NslUtilCsv.writeCsvFile(new File(outputFileName),outputList);

        long endTime=System.currentTimeMillis(); //获取结束时间

        System.out.println("程序运行时间： "+(endTime-startTime)/1000+"s");
        System.out.println("程序运行时间： "+(endTime-startTime)+"ms");




    }

    public static double getDistanceMeter(GlobalCoordinates gpsFrom, GlobalCoordinates gpsTo, Ellipsoid ellipsoid){

        //创建GeodeticCalculator，调用计算方法，传入坐标系、经纬度用于计算距离
        //GeodeticCalculator geoCurve = new GeodeticCalculator().calculateGeodeticCurve(ellipsoid, gpsFrom, gpsTo);
        return new GeodeticCalculator().calculateGeodeticCurve(ellipsoid, gpsFrom, gpsTo).getEllipsoidalDistance();

    }

    public static double getDistance(Point2D.Double originPoint, Point2D.Double offsetPoint){
        return getDistance(originPoint.getY(),originPoint.getX(),offsetPoint.getY(),offsetPoint.getX());
    }

    public static boolean isInPolygonByJdk(Point2D.Double point, NslPolygon polygon){
        List<Point2D.Double> pointList=polygon.getPointList();
        return check(point,pointList);
    }


    /**
     * 返回一个点是否在一个多边形区域内， 如果点位于多边形的顶点或边上，不算做点在多边形内，返回false
     *
     * @param point   要判断的点的横纵坐标
     * @param polygon 组成的顶点坐标集合
     * @return
     */
    public static boolean isInPolygonByJdk(Point2D.Double point, List<Point2D.Double> polygon){
        return check(point,polygon);
    }


    /**
     * 返回一个点是否在一个多边形区域内， 点在边界或者定点上 返回值不明确
     *
     * @param pointLon 要判断的点的横坐标 经度
     * @param pointLat 要判断的点的纵坐标 维度
     * @param lon      区域各顶点的横坐标数组
     * @param lat      区域各顶点的纵坐标数组
     * @return
     */
    public static boolean isInPolygonByJdk(double pointLon, double pointLat, Double[] lon,
                                           Double[] lat) {
        // 将要判断的横纵坐标组成一个点
        Point2D.Double point = new Point2D.Double(pointLon, pointLat);
        // 将区域各顶点的横纵坐标放到一个点集合里面
        List<Point2D.Double> pointList = new ArrayList<Point2D.Double>();
        double polygonPoint_x = 0.0, polygonPoint_y = 0.0;
        for (int i = 0; i < lon.length; i++) {
            polygonPoint_x = lon[i];
            polygonPoint_y = lat[i];
            Point2D.Double polygonPoint = new Point2D.Double(polygonPoint_x, polygonPoint_y);
            pointList.add(polygonPoint);
        }
        return check(point, pointList);
    }

    /**
     * @param point   要判断的点的横纵坐标
     * @param polygon 组成的顶点坐标集合
     * @return
     */
    private static boolean check(Point2D.Double point, List<Point2D.Double> polygon) {
        java.awt.geom.GeneralPath peneralPath = new java.awt.geom.GeneralPath();

        Point2D.Double first = polygon.get(0);
        // 通过移动到指定坐标（以双精度指定），将一个点添加到路径中
        peneralPath.moveTo(first.x, first.y);
        //polygon.remove(0);
        for (int i=1;i<polygon.size();i++) {
            // 通过绘制一条从当前坐标到新指定坐标（以双精度指定）的直线，将一个点添加到路径中。
            peneralPath.lineTo(polygon.get(i).x, polygon.get(i).y);
        }
        // 将几何多边形封闭
        peneralPath.lineTo(first.x, first.y);
        peneralPath.closePath();
        // 测试指定的 Point2D 是否在 Shape 的边界内。
        return peneralPath.contains(point);
    }

    /**
     * 判断点是否在多边形内，如果点位于多边形的顶点或边上，也算做点在多边形内，直接返回true
     * @param point 检测点
     * @param pts   多边形的顶点
     * @return      点在多边形内返回true,否则返回false
     */
    public static boolean isInPolygonByRay(Point2D.Double point, List<Point2D.Double> pts){

        int N = pts.size();
        boolean boundOrVertex = true; //如果点位于多边形的顶点或边上，也算做点在多边形内，直接返回true
        int intersectCount = 0;//cross points count of x
        double precision = 2e-10; //浮点类型计算时候与0比较时候的容差
        Point2D.Double p1, p2;//neighbour bound vertices
        Point2D.Double p = point; //当前点

        p1 = pts.get(0);//left vertex
        for(int i = 1; i <= N; ++i){//check all rays
            if(p.equals(p1)){
                return boundOrVertex;//p is an vertex
            }

            p2 = pts.get(i % N);//right vertex
            if(p.x < Math.min(p1.x, p2.x) || p.x > Math.max(p1.x, p2.x)){//ray is outside of our interests
                p1 = p2;
                continue;//next ray left point
            }

            if(p.x > Math.min(p1.x, p2.x) && p.x < Math.max(p1.x, p2.x)){//ray is crossing over by the algorithm (common part of)
                if(p.y <= Math.max(p1.y, p2.y)){//x is before of ray
                    if(p1.x == p2.x && p.y >= Math.min(p1.y, p2.y)){//overlies on a horizontal ray
                        return boundOrVertex;
                    }

                    if(p1.y == p2.y){//ray is vertical
                        if(p1.y == p.y){//overlies on a vertical ray
                            return boundOrVertex;
                        }else{//before ray
                            ++intersectCount;
                        }
                    }else{//cross point on the left side
                        double xinters = (p.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;//cross point of y
                        if(Math.abs(p.y - xinters) < precision){//overlies on a ray
                            return boundOrVertex;
                        }

                        if(p.y < xinters){//before ray
                            ++intersectCount;
                        }
                    }
                }
            }else{//special case when ray is crossing through the vertex
                if(p.x == p2.x && p.y <= p2.y){//p crossing over p2
                    Point2D.Double p3 = pts.get((i+1) % N); //next vertex
                    if(p.x >= Math.min(p1.x, p3.x) && p.x <= Math.max(p1.x, p3.x)){//p.x lies between p1.x & p3.x
                        ++intersectCount;
                    }else{
                        intersectCount += 2;
                    }
                }
            }
            p1 = p2;//next ray left point
        }
        if(intersectCount % 2 == 0){//偶数在多边形外
            return false;
        } else { //奇数在多边形内
            return true;
        }
    }


    public static void main(String[] args) throws IOException {

        GlobalCoordinates source = new GlobalCoordinates(34.10511, 114.45443);
        GlobalCoordinates target = new GlobalCoordinates(34.1071, 114.4545);

        double meter1 = getDistanceMeter(source, target, Ellipsoid.Sphere);
        double meter2 = getDistanceMeter(source, target, Ellipsoid.WGS84);

        System.out.println("Sphere坐标系计算结果："+meter1 + "米");
        System.out.println("WGS84坐标系计算结果："+meter2 + "米");

        String csvFile="D:\\GS_Dir_Ddisk\\GS#Programming\\Test\\points_L1800_DL.csv";

        String outputFile="D:\\GS_Dir_Ddisk\\GS#Programming\\Test\\points_L1800_DL_output.csv";

        groupByDistance(csvFile,outputFile,300);

    }
       /* for(int i=0; i<pointSet.size(); i++){
            System.out.println(i);
            Object name = it.next();
            if("a".equals(name) || "b".equals(name)){
                it.remove();
                i--;
            }

             while(it.hasNext()){
            CheckWork checkWork = it.next();
            if(checkWork.getState()==1){
                it.remove();
            }

        }*/

    /**
     * 以Y轴上半部分起始，顺时针为正值，，pointB 相对于 Point A 的方位角，输出结果为0-360之间
     * @param lat_a
     * @param lng_a
     * @param lat_b
     * @param lng_b
     * @return
     */
    public   static double getAzimuth(double lat_a, double lng_a, double lat_b, double lng_b) {

        double y = Math.sin(lng_b - lng_a) * Math.cos(lat_b);
        double x = Math.cos(lat_a) * Math.sin(lat_b) - Math.sin(lat_a) * Math.cos(lat_b) * Math.cos(lng_b - lng_a);
        double brng = Math.atan2(y, x);

        brng = Math.toDegrees(-brng);
        if (brng < 0)
            brng = brng + 360;

        //System.out.println(Math.atan(1));
        return brng;
    }
    /*
     * 将string:id,lon,lat的list转化成pointList
     */
    static public List<NslGpsPoint> convertStringList2PointList(List<String> lineList,boolean isHeadLineExist){
        int i=0;
        if(isHeadLineExist){
            i=1;
        }
        List<NslGpsPoint> NslGpsPointList=new ArrayList<>(lineList.size());
        for (;i<lineList.size();i++) {
            String[] array=lineList.get(i).split(",");
            double lon=Double.parseDouble(array[1]);
            double lat=Double.parseDouble(array[2]);
            NslGpsPoint NslGpsPoint=new NslGpsPoint(array[0],lon,lat);
            NslGpsPointList.add(NslGpsPoint);
        }
        return NslGpsPointList;
    }


    //经纬度转换成弧度
    static public List<NslGpsPoint> convertGpsList2RadianList(List<NslGpsPoint> NslGpsPointList){
        List<NslGpsPoint> NslGpsPointListRadian=new ArrayList<>(NslGpsPointList.size());
        for (NslGpsPoint NslGpsPoint : NslGpsPointList) {
            NslGpsPoint pointRadian=convertGps2Radian(NslGpsPoint);
            NslGpsPointListRadian.add(pointRadian);
        }
        return  NslGpsPointListRadian;
    }

    static public NslGpsPoint  convertGps2Radian(NslGpsPoint NslGpsPoint){
        NslGpsPoint pointRadian=new NslGpsPoint();
        if(NslGpsPoint.getCoordinate().equalsIgnoreCase("gps")){
            pointRadian.setLongitude(NslGpsPoint.getLongitude()/180*Math.PI);
            pointRadian.setLatitude(NslGpsPoint.getLatitude()/180*Math.PI);
            pointRadian.setCoordinate("Radian");
            pointRadian.setIdString(NslGpsPoint.getIdString());
        }else {
            pointRadian=NslGpsPoint;
        }

        return pointRadian;
    }


    /**
     * 将 浮点 类型的 点，转换成 Long 类型的点
     * @param point
     * @return
     */
    public static NslPointLong doublePoint2IntPoint(NslPoint point){
        Long x= Math.round(point.x());
        Long y=Math.round(point.y());
        NslPointLong pointLong=new NslPointLong(point.getId(),x,y);

        return pointLong;

    }

}
